package com.spring.sso.config.security.authentication;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.spring.sso.common.CommonResult;
import com.spring.sso.common.Constant;
import com.spring.sso.config.AuthenticationConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.*;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

/**
 * token验证过滤器
 * @author : pangfuzhong
 * @description
 * @date : 2021/9/16 14:20
 */
@Slf4j
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private AuthenticationConfig authenticationConfig;
    private AuthenticationEntryPoint authenticationEntryPoint;

    public TokenAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationConfig authenticationConfig) {
        super(authenticationManager);
        this.authenticationConfig = authenticationConfig;
    }

    public TokenAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationConfig authenticationConfig, AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
        this.authenticationConfig = authenticationConfig;
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    /**
     * description: 从request的header部分读取Token
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            // 1、获取token, 如果请求头中没有Authorization信息, 说明没有登陆, 调用认证中心返回当前服务名称对应的登陆地址(不同的web应用会有各自的登陆地址)
            String accessToken = this.getRequestAccessToken(request);
            if(StringUtils.isBlank(accessToken)) {
                // 未获取到accessToken, 走远程认证中心进行认证，即LoginAuthenticationFilter
                chain.doFilter(request, response);
                return;
            }

            // 2、认证token, 向认证服务器校验token, 可以远程校验或是本地比对
            Boolean result = this.authenticateTokenValid(accessToken);
            if (!result) {
                /**
                 * 2.1、token本身就失效, 返回当前服务对应登陆页面重新登陆
                 * 2.2、token认证连接超时或是调用异常, 本身token可能并未失效, 此种情况直接抛出异常,无需重新认证以及返回登陆页(本服务认证失败并不能排除别的web应用认证不成功)
                 * 2.3
                 * */
                throw new RuntimeException("token is invalid");
            }

            // 3、向下游推进
            super.doFilterInternal(request, response, chain);
        } catch (BadCredentialsException e) {
            this.authenticationEntryPoint.commence(request, response, e);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取请求头中的accessToken
     * @param request
     * @return accessToken
     * */
    private String getRequestAccessToken(HttpServletRequest request) {
        String tokenHeader = request.getHeader("Authorization");
        if (tokenHeader == null || !tokenHeader.startsWith("Bearer")) {
            return "";
        }

        return tokenHeader.replace("Bearer", "");
    }

    /**
     * 认证token
     * @param accessToken
     * @return
     * */
    private Boolean authenticateTokenValid(String accessToken) {
        String appName = this.authenticationConfig.getAppName();
        Assert.hasText(appName, "appName is null, please config in application.properties");
        Assert.hasText(accessToken, "accessToken is null, validation failure");
        RestTemplate restTemplate = this.authenticationConfig.getRestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setBearerAuth(accessToken);
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(null, headers);
        // --> 请求认证中心 POST /sso/channel/token/auth 地址进行认证, 该地址认证中心不拦截
        ResponseEntity<CommonResult> responseEntity = restTemplate.exchange(this.authenticationConfig.getServerTokenAuthUrl(), HttpMethod.POST, requestEntity, CommonResult.class);
        CommonResult commonResult = responseEntity.getBody();
        Boolean result = Boolean.FALSE;
        log.info("--> token认证 {}, appName = {}, accessToken = {}", (result = ((Boolean) commonResult.getData())) ? "成功":"失败", appName, accessToken);
        return result;
    }
}
